/*
 * file name:module_time.c
 * version :v1.0.0
 * Change Logs:
 * Date             Author      	main change
 * 2024-09-30     	lanxin          the first version
 * 2024-10-27       lanxin          recode soft timer
 * 2025-03-12       lanxin          add period timer
 * 
*/

/********************************************* READ ME BEGIN *************************************************************/

/* Brief:
 *
 * *1.基于 Unix 时间戳的软件定时器。
 *
 */

/* Notice:
 *
 * *1.call soft_tmr_schedule and put current time every second.
 *
 * *2.tmr_cb should not have any delay function.
 * 
 */

/********************************************* READ ME END ***************************************************************/

/* source file */
#include "rtdevice.h"

#include "soft_timer.h"

/* private define */
#define FS_ASSERT(PARA)                            	if((PARA) == 0) { rt_kprintf("file:%s",__FILE__);RT_ASSERT(0);}
#define FS_PARA_CHECK_NULL(PARA)                   	if((PARA) == 0) {rt_kprintf("file:%s line:%d para is null\r\n",__FILE__,__LINE__); return;}
#define FS_PARA_CHECK_NULL_THEN_RETURN(PARA,VALUE) 	if((PARA) == 0) {rt_kprintf("file:%s line:%d para is null\r\n",__FILE__,__LINE__); return VALUE;}
#define FS_MUTEX_LOCK(mutex)                		if(mutex) rt_mutex_take(mutex,RT_WAITING_FOREVER)
#define FS_MUTEX_LOCK_TIME(mutex,TIME)      		if(mutex) rt_mutex_take(mutex,TIME)
#define FS_MUTEX_UNLOCK(mutex)              		if(mutex) rt_mutex_release(mutex)

/* variable declaration */
typedef struct soft_timer
{
	rt_list_t list_tmr;
	time_t t_triger;
	uint32_t period;

	void *argv;
	tmr_cb_t cb;
} s_soft_tmr_t;

typedef struct timer_manage
{
	rt_list_t list_tmr;
	time_t t_now;
	struct rt_work work;
	struct rt_workqueue *work_queue;

	rt_mutex_t mutex_lock;
} s_tmr_manage_t;

static s_tmr_manage_t *tmr_manage = RT_NULL;

/* function declaration */

static int soft_tmr_show_tmr ( void )
{
	if ( tmr_manage == 0 )
	{
		rt_kprintf ( "soft timer is not init\r\n" );
		return -1;
	}

	if ( rt_list_isempty ( &tmr_manage->list_tmr ) )
	{
		rt_kprintf ( "there is no soft timer\r\n" );
		return -1;
	}

	s_soft_tmr_t *tmr = RT_NULL;
	FS_MUTEX_LOCK ( tmr_manage->mutex_lock );
	rt_list_t *tmp = 0;
	rt_list_for_each ( tmp, &tmr_manage->list_tmr )
	{
		tmr = rt_list_entry ( tmp, s_soft_tmr_t, list_tmr );

		if ( tmr )
			rt_kprintf ( "soft time:trigger %d period:%d\r\n", ( uint32_t ) tmr->t_triger, tmr->period );
	}
	FS_MUTEX_UNLOCK ( tmr_manage->mutex_lock );

	return 0;
}

static int insert_soft_tmr ( s_soft_tmr_t *tmr, time_t t_triger, tmr_cb_t cb, void *argv, uint32_t period )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( tmr, -1 );

	if ( t_triger < time ( 0 ) )
		return -1;

	FS_PARA_CHECK_NULL_THEN_RETURN ( tmr_manage, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( cb, -1 );

	tmr->argv = argv;
	tmr->cb = cb;
	tmr->t_triger = t_triger;
	tmr->period = period;
	rt_list_init ( &tmr->list_tmr );

	/* there is no timer,insert directly */
	if ( rt_list_isempty ( &tmr_manage->list_tmr ) )
	{
		rt_list_insert_after ( &tmr_manage->list_tmr, &tmr->list_tmr );
		return 0;
	}
	/* insert by increase time */
	else
	{
		s_soft_tmr_t *tmr_tmp = RT_NULL;
		rt_list_t *pos = 0, *tmp = 0;
		rt_list_for_each_safe ( pos, tmp, &tmr_manage->list_tmr )
		{
			tmr_tmp = rt_list_entry ( pos, s_soft_tmr_t, list_tmr );

			if ( tmr_tmp && t_triger <= tmr_tmp->t_triger )
			{
				rt_list_insert_before ( &tmr_tmp->list_tmr, &tmr->list_tmr );
				return 0;
			}
		}

		/* insert at last */
		rt_list_insert_before ( &tmr_manage->list_tmr, &tmr->list_tmr );
	}

	return 0;
}

static void tmr_work_handle ( struct rt_work *work, void *argv )
{
	s_tmr_manage_t *tmr = ( s_tmr_manage_t * ) argv;

	if ( tmr == 0 )
		return;

	if ( rt_list_isempty ( &tmr->list_tmr ) )
		return;

	FS_MUTEX_LOCK ( tmr->mutex_lock );

	s_soft_tmr_t *s_tmr = RT_NULL;
	rt_list_t *pos = 0, *tmp = 0;
	rt_list_for_each_safe ( pos, tmp, &tmr_manage->list_tmr )
	{
		s_tmr = rt_list_entry ( pos, s_soft_tmr_t, list_tmr );

		if ( s_tmr == 0 || s_tmr->t_triger > tmr->t_now )
			continue;

		if ( s_tmr->cb )
			s_tmr->cb ( s_tmr->t_triger, s_tmr->argv );

		rt_list_remove ( &s_tmr->list_tmr );
		if ( s_tmr->period == 0 )
		{
			rt_free ( s_tmr );
			continue;
		}

		/* reinsert */
		insert_soft_tmr ( s_tmr, s_tmr->t_triger + s_tmr->period, s_tmr->cb, s_tmr->argv, s_tmr->period );
	}

	FS_MUTEX_UNLOCK ( tmr->mutex_lock );
}

//when the time go to t_triger,call cb(t_triger,argv) ,if period >0 it will create one new timer (t_triger + period)
//t_triger should be after time now;
rt_list_t *soft_tmr_create ( time_t t_triger, tmr_cb_t cb, void *argv, uint32_t period )
{
	s_soft_tmr_t *new_tmr =  rt_calloc ( 1, sizeof ( s_soft_tmr_t ) );
	FS_PARA_CHECK_NULL_THEN_RETURN ( new_tmr, 0 );

	FS_MUTEX_LOCK ( tmr_manage->mutex_lock );
	int res = insert_soft_tmr ( new_tmr, t_triger, cb, argv, period );
	FS_MUTEX_UNLOCK ( tmr_manage->mutex_lock );

	return res == 0 ? &new_tmr->list_tmr : 0;
}

int soft_tmr_delete ( rt_list_t *list_tmr )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( list_tmr, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( tmr_manage, -1 );

	FS_MUTEX_LOCK ( tmr_manage->mutex_lock );
	s_soft_tmr_t *tmr = rt_list_entry ( list_tmr, s_soft_tmr_t, list_tmr );

	if ( tmr )
	{
		rt_list_remove ( &tmr->list_tmr );
		rt_free ( tmr );
	}

	FS_MUTEX_UNLOCK ( tmr_manage->mutex_lock );
	return 0;
}

int soft_tmr_set_period ( rt_list_t *list_tmr, uint32_t period )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( list_tmr, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( tmr_manage, -1 );

	FS_MUTEX_LOCK ( tmr_manage->mutex_lock );
	s_soft_tmr_t *tmr = rt_list_entry ( list_tmr, s_soft_tmr_t, list_tmr );

	if ( tmr )
	{
		tmr->period = period;
	}

	FS_MUTEX_UNLOCK ( tmr_manage->mutex_lock );
	return tmr ? 0 : -1;
}

//when the system time recalibrate,call it to clear all timer
void soft_tmr_delete_all ( void )
{
	rt_kprintf ( "clear all soft tmr\r\n" );

	if ( rt_list_isempty ( &tmr_manage->list_tmr ) )
		return ;

	FS_MUTEX_LOCK ( tmr_manage->mutex_lock );

	do
	{
		s_soft_tmr_t *tmr = rt_list_first_entry ( &tmr_manage->list_tmr, s_soft_tmr_t, list_tmr );

		if ( tmr )
		{
			rt_list_remove ( &tmr->list_tmr );
			rt_free ( tmr );
		}
		else
		{
			break;
		}

	}
	while ( ! ( rt_list_isempty ( &tmr_manage->list_tmr ) ) );

	FS_MUTEX_UNLOCK ( tmr_manage->mutex_lock );
}

//call it every second times
void soft_tmr_schedule ( time_t t_now )
{
	if ( t_now <= 0 )
		return;
		
	if ( tmr_manage && tmr_manage->work_queue )
	{
		tmr_manage->t_now = t_now;
		rt_workqueue_urgent_work ( tmr_manage->work_queue, &tmr_manage->work );
	}
}

//priority stack_size is to set wq thread
void soft_tmr_init ( rt_uint8_t priority, rt_uint16_t stack_size )
{
	if ( tmr_manage )
	{
		rt_kprintf ( "soft timer is already init\r\n" );
		return;
	}

	tmr_manage = rt_calloc ( sizeof ( s_tmr_manage_t ), 1 );
	FS_ASSERT ( tmr_manage );

	tmr_manage->t_now = time ( 0 );
	tmr_manage->mutex_lock = rt_mutex_create ( "tmrlock", RT_IPC_FLAG_FIFO );
	FS_ASSERT ( tmr_manage->mutex_lock );

	rt_list_init ( &tmr_manage->list_tmr );

	rt_work_init ( &tmr_manage->work, tmr_work_handle, tmr_manage );
	tmr_manage->work_queue = rt_workqueue_create ( "tmrwq", stack_size, priority );
	FS_ASSERT ( tmr_manage->work_queue );
}

/* 导出到初始化列表 */

/* 导出到 MSH 命令列表 */
MSH_CMD_EXPORT ( soft_tmr_show_tmr, show soft time );
